package org.slusk.thynwor.neat;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.jgap.BulkFitnessFunction;
import org.jgap.Chromosome;
import org.slusk.thynwor.corestructures.Environment;
import org.slusk.thynwor.util.Util;

import com.anji.integration.Activator;
import com.anji.integration.ActivatorTranscriber;
import com.anji.integration.TranscriberException;
import com.anji.neat.Evolver;
import com.anji.util.Properties;

public class Bo4FitnessFunction implements BulkFitnessFunction {

	private static final long serialVersionUID = -5913552257257746198L;

	private static double thresholdForSerialization = 55;
	private static final String BASE_FILE_NAME_FOR_SERIALIZATION = "Bo4Net";
	private static final String DIRECTORY_FOR_SERIALIZATION = "serialized";
	private static final String sep;
	private static Properties props;
	
//	private static int watchLevel = 1;
			
	private double fitnessRecord = -40000;
	private int subjectCount = 0;
	private int generationCount = 0;

	private int simulationsToRun = 4;

	private int timeSteps = 75;

	private static int foodCollectedWgt = 100;
	private static int moveFoodTowardGoalWgt = 10;
	private static int assistWithFoodWgt = 15;
	private static int attachedToFoodWgt = 3;
	private static int handsFullWgt = -2;
	private static int hitWallWgt = -3;
	
	
	private static Logger logger;
	private static FileHandler fileTxt;
	private static SimpleFormatter formatterTxt;
	
	
	static {
		sep = System.getProperty("file.separator");
		
		// Create Logger
		logger = Logger.getLogger(Bo4FitnessFunction.class.getName());
		logger.setLevel(Level.INFO);
		try {
			fileTxt = new FileHandler("bo4ff.log");
			formatterTxt = new SimpleFormatter();
			fileTxt.setFormatter(formatterTxt);
			logger.addHandler(fileTxt);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	public Bo4FitnessFunction() {

	}
	
	public void evaluate(List<Chromosome> subjects) {
		subjectCount = 0;
		logger.info("Beginning generation " + generationCount + ".");
		for (Object o : subjects) {
			Chromosome ch = (Chromosome) o;
			
			ch.setFitnessValue(evaluate(ch));
			subjectCount++;
		}
		logger.info("Completed generation " + generationCount + "; " + subjectCount + " individuals evaluated.");
		generationCount++;
	}

	public int getMaxFitnessValue() {
		return 20000;
	}

	public int evaluate(Chromosome genoType) {
		ArrayList<Integer> fitnessArray = new ArrayList<Integer>(simulationsToRun);
		int fitness = 0;
		
		ActivatorTranscriber at = new ActivatorTranscriber();
		try {
			at.init(props);
		} catch (TranscriberException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			return 0;
		}
		
		ActivatableNet net;
		try {
			net = new AnjiNetWrapper(at.newActivator(genoType));
		} catch (TranscriberException e) {
			e.printStackTrace();
			return 1;
		}
		
		for (int i = 0; i < simulationsToRun; i++) {
			Environment e = new Environment();
			e.setBo4Net(net);
			
			e.runSim(timeSteps);
			
			fitnessArray.add(calculateFitness(e));	
		}
		
		fitness = averageResults(fitnessArray);
		
		if (fitness > fitnessRecord) {

//			System.out.println("EvalCount:" + subjectCount + ", fitnessRecord:" + fitness);
			logger.info("Generation: " + generationCount + ", Subject: " + subjectCount + ", fitnessRecord: " + fitness);
			fitnessRecord = fitness;
			
			if (fitness > thresholdForSerialization) {
				thresholdForSerialization += 150;
				//Demo
//				Environment e = new Environment();
//				e.setBo4Net(net);
//				e.createWindow();
//				e.runSim(40);
//				e.finalize();
				
				// Save; Probably not the best place for this
				// For now, don't serialize
//				String fileName = DIRECTORY_FOR_SERIALIZATION + sep + BASE_FILE_NAME_FOR_SERIALIZATION + "_" + timeStamp() + "_f" + fitness + ".ser";
//				if (Util.serializeChromosome(fileName, genoType)) {
//					logger.info("Serialized current fitness winner to: " + fileName);
//				} else {
//					logger.log(Level.SEVERE, "Failed to save file: " + fileName);
//				}
			}
		} 

		subjectCount++;
		
		return fitness;
		
	}
	

	private int calculateFitness(Environment e) {
		
		int foodCollected = e.getFoodCollected() * foodCollectedWgt;
		
		int moveFoodTowardGoal = e.getMoveFoodTowardGoal() * moveFoodTowardGoalWgt;
		
		int assistWithFood = e.getAssistWithFood() * assistWithFoodWgt;
		
		int attachedToFood = e.getAttachedToFood() * attachedToFoodWgt;
		
		int handsFull = e.getHandsFull() * handsFullWgt;

		int hitWall = e.getHitObstacle() * hitWallWgt;

		int fitness = foodCollected
				+ moveFoodTowardGoal
				+ assistWithFood
				+ attachedToFood
				+ handsFull
				+ hitWall;
		
//		if (fitness < 1) fitness = 1;
		
		return fitness;
	}

	public static int averageResults(ArrayList<Integer> errList) {
		double result = 0;
		for (Integer err : errList) {
			result += err;
		}
		return (int) Math.round(result / errList.size());
	}
	

	public static int getMax(double[] output) {
		int idx = 0;
		for (int i = 1; i < output.length; i++) {
			if (output[i] > output[idx]) 
				idx = i;
		}
		return idx;
	}
	
	public static boolean save(String fileName, Chromosome genoType) {
		boolean saveOk = false;
		ObjectOutputStream s = null;
		FileOutputStream out = null;
		try {
			if (fileName != null)
			//System.out.println("Saving Best Chromosome to " + fileName);
			out = new FileOutputStream(fileName);
			s = new ObjectOutputStream(out);
			s.writeObject(genoType);
			s.flush();
			saveOk = true;
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				if (s != null) {
					s.close();
				}
				
				if (out != null) {
					out.close();
				}
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
		
		//System.out.println("Saving Best Chromosome...Done");
		return (saveOk);
	}
	
	public static String timeStamp() {
		Calendar calendar = Calendar.getInstance();
		StringBuilder sb = new StringBuilder();
		sb.append(calendar.get(Calendar.YEAR));
		sb.append(calendar.get(Calendar.MONTH));
		sb.append(calendar.get(Calendar.DAY_OF_MONTH));
		sb.append(".");
		sb.append(calendar.get(Calendar.HOUR_OF_DAY));
		sb.append(".");
		sb.append(calendar.get(Calendar.MINUTE));
		sb.append(".");
		sb.append(calendar.get(Calendar.SECOND));
		return sb.toString();
	}
	
	public static void main( String[] args ) throws Throwable {
		try {
			if ( args.length != 1 ) {
//				Evolver.usage();
				System.exit( -1 );
			}

			// Anji Properties class... file must be in classpath 
			Properties props = new Properties( args[ 0 ] );
			Bo4FitnessFunction.props = props;
			
			Evolver evolver = new Evolver();
			
			evolver.init( props );
			evolver.run();
			System.exit( 0 );
		}
		catch ( Throwable th ) {
			logger.log(Level.SEVERE, th.getMessage());
			throw th;
		}
	}


	public boolean endRun() {
		// TODO Auto-generated method stub
		return false;
	}

	public void dispose() {
		// TODO Auto-generated method stub
		
	}	
}

